package com.bitcola.security.activity.biz;


import com.bitcola.entity.ColaMeBalance;
import com.bitcola.security.activity.entity.*;
import com.bitcola.security.activity.mapper.*;
import com.bitcola.security.common.util.Snowflake;
import lombok.extern.log4j.Log4j2;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Service
@Log4j2
public class DefiMiningBiz {

    @Autowired
    BalanceMapper balanceMapper;

    @Autowired
    ColaUserBalanceMapper colaUserBalanceMapper;


    @Autowired
    RedisTemplate<String, Object> redisTemplate;


    @Autowired
    DexProjectMapper dexProjectMapper;

    @Autowired
    UserInviterBiz userInviterBiz;

    @Autowired
    DefiMiningBiz defiMiningBiz;

    @Autowired
    BalanceChangeBiz balanceChangeBiz;

    String powersKey = "powerkey";
    @Autowired
    BalanceChangeLogMapper balanceChangeLogMapper;


    Map<String, DexMiningProject> projectMap = new ConcurrentHashMap<>();
    Map<String, BigDecimal> userBalance = new ConcurrentHashMap<>();
    Map<String, List<ColaMeBalance>> balanceRank = new ConcurrentHashMap<>();
    public static Map<String, AtomicInteger> miningNumMap = new ConcurrentHashMap<>();

    public Double getDailyPower() {


        Double result = (Double) redisTemplate.opsForValue().get(powersKey);
        return result == null ? 1000d : result;


    }

    @Scheduled(cron = "0/59 * * * * ? ")

    public void cacheProject() {
        var data = dexProjectMapper.selectAll();
        data.forEach(x -> {
            projectMap.put(x.getCoinCode(), x);
        });

    }

    public void setBest(String coinCode, BigDecimal v) {
        redisTemplate.opsForValue().set("best_" + coinCode, v);

    }

    public BigDecimal getBest(String coinCode) {
        var v = redisTemplate.opsForValue().get("best_" + coinCode);
        if (v == null) return BigDecimal.ZERO;
        return new BigDecimal(v.toString());
    }

    public BigDecimal getTotalGain(String coinCode, String userId) {
        var v = redisTemplate.opsForValue().get("totalGain_" + coinCode + "_" + userId);
        if (v == null) return BigDecimal.ZERO;
        return new BigDecimal(v.toString());
    }

    public BigDecimal getTotalProduct(String coinCode) {
        var v = redisTemplate.opsForValue().get("totalProduct_" + coinCode);
        if (v == null) return BigDecimal.ZERO;
        return new BigDecimal(v.toString());
    }

    public void setTotalGain(String coinCode, String userId, BigDecimal v) {
        redisTemplate.opsForValue().set("totalGain_" + coinCode + "_" + userId, v);
    }

    public void setTotalProduct(String coinCode, BigDecimal v) {
        redisTemplate.opsForValue().set("totalProduct_" + coinCode, v);

    }


    public void caculateBest() {
        for (String coinCode : projectMap.keySet()) {
            var ranks = balanceRank.get(coinCode);

            BigDecimal min = projectMap.get(coinCode).getMinNumber();
            List<Integer> balanceRank = new ArrayList<>(ranks.size());
            int rank = 1;
            Long sum = 1l;
            BigDecimal current = ranks.get(0).getBalanceAvailable();
            balanceRank.add(1);
            for (int i = 1; i < ranks.size(); i++) {
                if (!ranks.get(i).getBalanceAvailable().equals(current)) {
                    rank = (i + 1);
                    current = ranks.get(i).getBalanceAvailable();
                }
                sum += rank;
                balanceRank.add(rank);
            }
            log.info("持币全网排名总算力：" + sum);
            log.info("资金排名列表长度： " + ranks.size());
            log.info("最小持币： " + min.toPlainString());
            BigDecimal best = BigDecimal.ZERO;
            BigDecimal actor = new BigDecimal(Integer.MAX_VALUE);
            for (int i = 0; i < ranks.size(); i++) {
                var b = ranks.get(i);
                var balance = getUserBalanceCache(coinCode, b.getUserId());
                if (balance.compareTo(min) >= 0) {
                    int r = balanceRank.get(i);
                    BigDecimal c = balance.divide(BigDecimal.valueOf(r), 5, RoundingMode.DOWN);
                    if (c.compareTo(actor) < 0) {
                        best = balance;
                        actor = c;
                    }

                }
            }
            log.info("当前最佳:" +best.toPlainString());
            setBest(coinCode, best);
        }


    }

    public void addMiningNum(String userId, String coinCode, Map<String, AtomicInteger> tempMap,BigDecimal min) {


        var net = UserInviterBiz.userNetMap.get(userId);
        var netTemp = net;

        if(getUserBalanceCache(coinCode,userId).compareTo(min)>=0) {
            log.info("用户:"+userId+" 常与"+coinCode+"挖矿");
            if (net.getInviter() != null) {

                while (netTemp.getInviter() != null) {
                    var inviter = UserInviterBiz.userNetMap.get(netTemp.getInviter());
                    if (inviter != null) {

                        var map = tempMap.get(inviter.getUserId() + coinCode);
                        if (map == null) map = new AtomicInteger(0);

                        map.incrementAndGet();
                        tempMap.put(inviter.getUserId() + coinCode, map);

                        netTemp = inviter;


                    }
                }
            }
        }

    }

    @Scheduled(cron = "0 0/3 * * * ? ")

    public void cacheUserBalance() {
        for (String coinCode : projectMap.keySet()) {
            Example example = new Example(ColaMeBalance.class);
            example.createCriteria().andEqualTo("coinCode", coinCode);
            var balances = balanceMapper.selectByExample(example);
            for (int i = 0; i < balances.size(); i++) {
                var b = balances.get(i);
                var net = UserInviterBiz.userNetMap.get(b.getUserId());
                if (net != null) {
                    net.setSelfPower(b.getBalanceAvailable());
                }
                userBalance.put(b.getUserId() + coinCode, b.getBalanceAvailable());
            }
            var tempMap = new ConcurrentHashMap<String, AtomicInteger>();

            var min=projectMap.get(coinCode).getMinNumber();
            for (int i = 0; i < balances.size(); i++) {
                var b = balances.get(i);
                 addMiningNum(b.getUserId(), coinCode, tempMap,min);
            }
            miningNumMap = tempMap;
            balances.sort((x1, x2) -> {
                return x1.getBalanceAvailable().compareTo(x2.getBalanceAvailable());
            });
            balanceRank.put(coinCode, balances);
        }
        caculateBest();

    }

    public void setDailyPower(Double power) {

        redisTemplate.opsForValue().set(powersKey, power);

    }


    public BigDecimal getSharePower(String userId, String coinCode, BigDecimal min) {


        var net = UserInviterBiz.userNetMap.get(userId);
        BigDecimal netPower = BigDecimal.ZERO;
        if (net != null) {
            var child = net.getChild();
            BigDecimal max = BigDecimal.ZERO;
            BigDecimal sum = BigDecimal.ZERO;
            for (UserNet c : child) {
                BigDecimal hold = getUserBalanceCache(coinCode, c.getUserId());
                if (hold.compareTo(min) >= 0) {
                    if (hold.compareTo(max) > 0) max = hold;
                    sum = sum.add(hold);
                }
            }
            sum = sum.subtract(max).add(BigDecimal.valueOf(Math.pow(max.doubleValue(), 1.0 / 3.0))).setScale(4, RoundingMode.DOWN);
            if (sum.compareTo(BigDecimal.ZERO) > 0)
                log.info("用户：" + userId + "拥有直推挖矿用户算力之和：" + sum.toPlainString());
            netPower = netPower.add(sum);
        }
        return netPower;

    }

    public void shareMining() {

        for (String coinCode : projectMap.keySet()) {

            var balances = balanceRank.get(coinCode);
            BigDecimal min = projectMap.get(coinCode).getMinNumber();

            List<BigDecimal> powerList = new ArrayList<>(balances.size());
            BigDecimal netPower = BigDecimal.ZERO;
            for (ColaMeBalance balance : balances) {
                var sum = getSharePower(balance.getUserId(), coinCode, min);

                netPower = netPower.add(sum);

                powerList.add(sum);
            }


            log.info("分享挖矿全网算力之和：" + netPower);
            var p = getDailyPower();

            if (netPower.compareTo(BigDecimal.ZERO) > 0) {

                for (int i = 0; i < balances.size(); i++) {
                    double v = powerList.get(i).divide(netPower, 6, RoundingMode.DOWN).doubleValue();
                    var c = v * p * 0.5;
                    if (c > 0.1 && balances.get(i).getBalanceAvailable().compareTo(min) >= 0) {
                        log.info("用户 " + balances.get(i).getUserId() + " 分享挖矿发放成功：" + BigDecimal.valueOf(c).setScale(4, RoundingMode.DOWN));
                        defiMiningBiz.mining(balances.get(i).getUserId(), coinCode, BigDecimal.valueOf(c).setScale(4, RoundingMode.DOWN), "share");
                    }

                }
            }


        }

    }


    public void miningHold() {
        for (String coinCode : projectMap.keySet()) {
            var ranks = balanceRank.get(coinCode);
            List<Integer> balanceRank = new ArrayList<>(ranks.size());
            int rank = 1;
            Long sum = 1l;
            BigDecimal current = ranks.get(0).getBalanceAvailable();
            balanceRank.add(1);
            for (int i = 1; i < ranks.size(); i++) {
                if (!ranks.get(i).getBalanceAvailable().equals(current)) {
                    rank = (i + 1);
                    current = ranks.get(i).getBalanceAvailable();
                }
                sum += rank;
                balanceRank.add(rank);
            }
            log.info("持币全网排名总算力：" + sum);
            var min = projectMap.get(coinCode).getMinNumber();
            log.info("资金排名列表长度： " + ranks.size());
            log.info("最小持币： " + min.toPlainString());

            double power = getDailyPower() * 0.5;
            for (int i = 0; i < ranks.size(); i++) {
                var b = ranks.get(i);
                if (getUserBalanceCache(coinCode, b.getUserId()).compareTo(min) >= 0) {
                    int r = balanceRank.get(i);
                    double v = ((double) r) / ((double) sum);
                    log.info("用户：" + b.getUserId() + "排名" + r + "获得因子" + v);
                    defiMiningBiz.mining(b.getUserId(), coinCode, BigDecimal.valueOf(power * v).setScale(4, RoundingMode.DOWN), "mining");
                }
            }
        }
    }


    boolean checkHold(String userId, String coinCode) {
        var project = projectMap.get(coinCode);
        BigDecimal min = project.getMinNumber();
        var balance = getUserBalanceCache(coinCode, userId);
        return (balance.compareTo(min) >= 0);
    }

    public MiningActor getMiningActor(String userId, String coinCode) {
        var net = UserInviterBiz.userNetMap.get(userId);
        var actor = getMiningWork(userId, coinCode, net);
        var num =   miningNumMap.get(userId + coinCode);
        if (num != null) actor.setMiningNum(num.intValue());
        else actor.setMiningNum(0);

        for (var child : net.getChild()) {
            var childNet = getMiningWork(child.getUserId(), coinCode, child);
            num = miningNumMap.get(child.getUserId() + coinCode);
            if (num != null) childNet.setMiningNum(num.intValue());
            else childNet.setMiningNum(0);
            actor.getMiningActors().add(childNet);

        }
        return actor;
    }

    private MiningActor getMiningWork(String userId, String coinCode, UserNet net) {
        MiningActor miningActor = new MiningActor();
        miningActor.setAddress(net.getTel());
        miningActor.setActiveNum(net.getGroupNum().intValue());
        miningActor.setUserId(net.getUserId());
        miningActor.setCoinCode(coinCode);
        miningActor.setNumber(getUserBalanceCache(coinCode, userId));
        return miningActor;
    }

    BigDecimal getUserBalanceCache(String coinCode, String userId) {
        var v = userBalance.get(userId + coinCode);
        if (v != null)
            return v;
        return BigDecimal.ZERO;

    }

    Snowflake snowflake = new Snowflake(2, 14);
    @Autowired
    DexLogMapper dexLogMapper;

    @Transactional
    public void mining(String userId, String coinCode, BigDecimal number, String type) {
        balanceChangeBiz.createChange(userId, null, "mining", number, type, coinCode);
        colaUserBalanceMapper.addUserBanlance(userId, coinCode, number, null);
        DexMiningLog dexMiningLog = new DexMiningLog();
        dexMiningLog.setId(snowflake.nextIdStr());
        dexMiningLog.setType(type);
        dexMiningLog.setNumber(number);
        dexMiningLog.setTime(new Date());
        dexMiningLog.setUserId(userId);
        dexMiningLog.setCoinCode(coinCode);
        dexLogMapper.insert(dexMiningLog);
        BigDecimal userGain = getTotalGain(coinCode, userId);
        userGain = userGain.add(number);
        setTotalGain(coinCode, userId, userGain);
        BigDecimal totalProduct = getTotalProduct(coinCode);
        totalProduct = totalProduct.add(number);
        setTotalProduct(coinCode, totalProduct);

    }

    public static void main(String[] args) {
        Date date=new   Date();//取时间
        Calendar   calendar   =   new GregorianCalendar();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, -1);//
        calendar.set(Calendar.HOUR_OF_DAY,0);
        calendar.set(Calendar.MINUTE,0);
        calendar.set(Calendar.SECOND,0);
       var d= calendar.getTime();
        System.out.println(d);

    }
    public List<DexMiningProject> getList(String userId) {
        Date date=new   Date();//取时间
        Calendar   calendar   =   new GregorianCalendar();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, -1);//
        calendar.set(Calendar.HOUR_OF_DAY,0);
        calendar.set(Calendar.MINUTE,0);
        calendar.set(Calendar.MINUTE,0);
        calendar.set(Calendar.SECOND,0);
        var dexMiningDailyProfitMap=balanceChangeLogMapper.staticMiningProfit(userId,  calendar.getTime(),null ).stream().collect(Collectors.toMap(DexMiningDailyProfit::getCoin, v -> v, (v1, v2) -> v1));


        List<DexMiningProject> dexMiningProjectList = dexProjectMapper.selectAll();
        for (DexMiningProject dexMiningProject : dexMiningProjectList) {
            dexMiningProject.setTotalProduct(getTotalProduct(dexMiningProject.getCoinCode()));
            dexMiningProject.setSharePower(getSharePower(userId, dexMiningProject.getCoinCode(), dexMiningProject.getMinNumber()));
            dexMiningProject.setTotalGain(getTotalGain(dexMiningProject.getCoinCode(), userId));
            dexMiningProject.setBestNumber(getBest(dexMiningProject.getCoinCode()));
            var yesterdayProfit=dexMiningDailyProfitMap.get(dexMiningProject.getCoinCode());
            if(yesterdayProfit==null)            dexMiningProject.setYesterdayProfit(BigDecimal.ZERO);
            else dexMiningProject.setYesterdayProfit(yesterdayProfit.getNumber());

            var balance = getUserBalanceCache(dexMiningProject.getCoinCode(), userId);
            if (balance.compareTo(dexMiningProject.getMinNumber()) < 0) {
                dexMiningProject.setHoldPower(BigDecimal.valueOf(0));
            } else {
                dexMiningProject.setHoldPower(getUserBalanceCache(dexMiningProject.getCoinCode(), userId));
            }
            dexMiningProject.setBestNumber(getBest(dexMiningProject.getCoinCode()));
        }

        return dexMiningProjectList;

    }

    public List<DexMiningDailyProfit> getDailyProfit(String userId,String coinCode) {



        Date date=new   Date();//取时间
        Calendar   calendar   =   new GregorianCalendar();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, -8);//把日期往后增加一年.整数往后推,负数往前移动
        calendar.set(Calendar.HOUR_OF_DAY,0);
        calendar.set(Calendar.MINUTE,0);
        calendar.set(Calendar.MINUTE,0);
        calendar.set(Calendar.SECOND,0);
        List<DexMiningDailyProfit> dexMiningDailyProfitList=  balanceChangeLogMapper.staticMiningProfit(userId,calendar.getTime(),coinCode);
        dexMiningDailyProfitList.forEach(x->{
            x.setTime(x.getTimes().getTime());
        });

        return dexMiningDailyProfitList;

    }


    public List<DexMiningLog> getDexminingLogs(String userId, int page, int size) {

        Example example = new Example(DexMiningLog.class);
        example.createCriteria().andEqualTo("userId", userId);
        example.orderBy("time").desc();
        return dexLogMapper.selectByExampleAndRowBounds(example, new RowBounds((page - 1) * size, size));


    }

    public void prizeUser() {

        Example example = new Example(ColaMeBalance.class);
        var crit = example.createCriteria().andEqualTo("coinCode", "FB").andGreaterThanOrEqualTo("balanceAvailable", 50);
        example.orderBy("balanceAvailable").asc();
        var balances = balanceMapper.selectByExample(example);


    }

    public BigDecimal getProfit(String userId, String coinCode) {

        return getTotalGain(coinCode, userId);
    }


}
